﻿using System.Collections.Generic;
using System.Data.Cassandra.Pool;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Data.Cassandra.Connection.Pool;

namespace System.Data.Cassandra.Connection.Nodes
{
    public class ManualNodeEnumerator : ICassandraNodeEnumerator
    {
        private readonly ConnectionPool pool;

        private string[] originalNodes;
        private LinkedList<string> nodes;
        private HashSet<string> excludeNodes;

        protected ConnectionPool Pool
        {
            get
            {
                return pool;
            }
        }

        public ICollection<string> Nodes
        {
            get
            {
                return nodes;
            }
        }

        public ManualNodeEnumerator(ConnectionPool pool)
            : this(pool.Nodes)
        {
            this.pool = pool;
        }
        public ManualNodeEnumerator(IEnumerable<string> hosts)
        {
            SetNodes(hosts);
        }

        protected internal void SetNodes(IEnumerable<string> hosts)
        {
            SetNodesCore(hosts.ToArray(), excludeNodes);
        }

        private void SetNodesCore(string[] hosts, HashSet<string> excludes)
        {
            if (hosts == null || hosts.Length == 0)
            {
                throw new ArgumentNullException("hosts");
            }
            LinkedList<string> linkedList;
            if (excludes == null || excludes.Count == 0)
            {
                linkedList = new LinkedList<string>(hosts);
            }
            else
            {
                linkedList = new LinkedList<string>(
                    from h in hosts
                    where !excludes.Contains(h)
                    select h);
                if (linkedList.Count == 0)
                {
                    linkedList = new LinkedList<string>(hosts);
                }
            }
            originalNodes = hosts;
            excludeNodes = excludes;
            nodes = linkedList;
            OnNodesChanged(EventArgs.Empty);
        }

        protected virtual void OnNodesChanged(EventArgs e)
        {
            ConnectionPool connectionPool = pool;
            if (connectionPool != null)
            {
                ICollection<string> collection = Nodes;
                connectionPool.UpdateNodes(collection);
            }
        }

        protected void RoundRobin()
        {
            LinkedList<string> list = nodes;
            switch (list.Count)
            {
                case 0:
                    {
                        throw new InvalidOperationException("No hosts in the ring");
                    }
                case 1:
                    {
                        break;
                    }
                default:
                    {
                        LinkedListNode<string> first = list.First;
                        list.RemoveFirst();
                        list.AddLast(first);
                        break;
                    }
            }
        }

        public void SetExcludes(IEnumerable<string> excludes)
        {
            HashSet<string> hashSet = new HashSet<string>();
            foreach (string current in excludes)
            {
                hashSet.Add(current);
                try
                {
                    IPAddress[] hostAddresses = Dns.GetHostAddresses(current);
                    for (int i = 0; i < hostAddresses.Length; i++)
                    {
                        IPAddress iPAddress = hostAddresses[i];
                        hashSet.Add(iPAddress.ToString());
                    }
                }
                catch (SocketException)
                {
                }
            }
            if (hashSet.Count == 0)
            {
                if (excludeNodes != null)
                {
                    SetNodesCore(originalNodes, null);
                }
            }
            else
            {
                if (excludeNodes == null || !hashSet.SetEquals(excludeNodes))
                {
                    SetNodesCore(originalNodes, hashSet);
                }
            }
        }
        public IEnumerable<string> NextNodes()
        {
            RoundRobin();
            return nodes;
        }

        public virtual void UpdateNodes()
        {
        }
    }
}